package Script;

import interfaces.TableFile;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Set;

import Calculate.ByteCalc;

public class JTableFile implements TableFile {
	private File file;			// The tablefile itself.
	private int hexMaxLength;	// Length of the longest hex entry in the file.
	private int textMaxLength;	// Length of the longest text entry in the file.
	private String charSet;
	
	/* Table file data used for dumping and inserting. */
	private HashMap<TableValueWrapper, String> dumpTable;
	private HashMap<String, TableValueWrapper> insertTable;
	
	private SpecialCharSet specialCharSet;
	
	/**
	 * Constructor that allows a null value to
	 * be passed in order to only dump bytes directy.
	 * @throws IOException 
	 */
	public JTableFile(File tbl) throws IOException {
		file = tbl;
		
		dumpTable = new HashMap<TableValueWrapper, String>();
		insertTable = new HashMap<String, TableValueWrapper>();
		specialCharSet = new SpecialCharSet();
		
		charSet = "ISO-8859-1";
		
		/* 
		 * If this tablefile is initiated without
		 * a table, set hex length to 1 to allow
		 * reading of single bytes.
		 */
		if (tbl != null) {
			createTable();
		} else {
			hexMaxLength = 1;
		}
	}
	
	/**
	 * Used to specify a certain charset for the table file reader.
	 * @throws IOException 
	 */
	public JTableFile(File tbl, String charsetName) throws IOException {
		file = tbl;
		
		dumpTable = new HashMap<TableValueWrapper, String>();
		insertTable = new HashMap<String, TableValueWrapper>();
		specialCharSet = new SpecialCharSet();
		
		charSet = charsetName;
		
		createTable();
	}
	
	@Override
	public byte[] getBytes(String str) {
		TableValueWrapper temp = insertTable.get(str);
		
		/*
		 * If temp is null, check if there's a special charset.
		 * Otherwise, specialCharSet returns null.
		 */
		if (temp == null) {
			return specialCharSet.get(str);
		}
		
		/*
		 * If there's a regular match, extract data.
		 */
		return temp.getData();
	}
	
	@Override
	public String getString(byte[] bin) {
		String temp = dumpTable.get(new TableValueWrapper(bin));
		
		if (temp != null) {
			return temp;
		} else {
			
			/* 
			 * If the vaue has no regular entry in the
			 * table file, check if there are any matching
			 * special characters. Returns null if none are found.
			 */
			return specialCharSet.get(bin);
		}
	}
	
	@Override
	public int getHexMaxLength() {
		return hexMaxLength;
	}

	@Override
	public int getTextMaxLength() {
		return Math.max(textMaxLength, specialCharSet.getLongestTextEntry());
	}
	
	@Override
	public String getCharset() {
		return charSet;
	}
	
	@Override
	public void removeValues(int n) {
		Set<TableValueWrapper> dumpKeys = dumpTable.keySet();
		String key;
		byte[] data;
		
		/*
		 * For every key that's longer than n, remove it
		 * and its values from the dump table. Fetch its value
		 * and use it to remove it from the insert set as well.
		 */
		for (TableValueWrapper tvw : dumpKeys) {
			key = dumpTable.get(tvw);
			data = tvw.getData();
			
			if (key.length() > n || data.length > n) {
				dumpTable.remove(tvw);
				insertTable.remove(key);
			}
		}
	}
	
	/** 
	 * Initiates all tables with values read from the table file.
	 * 
	 * @throws IOException
	 */
	private void createTable() throws IOException {
		String hexValue;
		String textValue;
		char startValue;
		byte[] tblEntry;
		byte[] temp;
		int byteLength;
		
		/* Start by buffering the file about to be read. */
		//BufferedReader br = new BufferedReader(new FileReader(file));
		BufferedReader br = 
			new BufferedReader(
					new InputStreamReader(
							new FileInputStream(file), charSet));
		
		/* Stores one line from the file at a time. */
		String tableLine;
		
		/* Read each line and put it into the appropriate table */
		while ((tableLine = br.readLine()) != null && tableLine.length() > 0)
		{
			hexValue = getHexValue(tableLine);
			textValue = getTextValue(tableLine);
			
			/* 
			 * Only fill the table collections if both the hex and text
			 * values are valid.
			 */
			if (hexValue != null && textValue != null) {
				byteLength = (int) Math.ceil( hexValue.length() / 2.0);
				
				/* Update entry lengths. */
				setHexMaxLength(byteLength);
				setTextMaxLength(textValue.length());
				
				/* Convert hex to bytes */
				tblEntry = getBytes(Long.valueOf(hexValue, 16), byteLength);
				
				/* Put entries in tablefiles. */
				dumpTable.put(new TableValueWrapper(tblEntry), textValue);
				insertTable.put(textValue, new TableValueWrapper(tblEntry));
				
			/* 
			 * If the line is not valid, but still is a special character,
			 * insert it.
			 */
			} else if (specialCharSet.isSpecialCharacter(
										tableLine.substring(0, 1))) {
				
				startValue = tableLine.charAt(0);
				
				/* Get the hex value. */
				hexValue = tableLine.substring(1, tableLine.length());
				byteLength = (int) Math.ceil( hexValue.length() / 2.0);
				
				if (isValidHex(hexValue)) {
					
					/* Check hex length. */
					setHexMaxLength(byteLength);
					
					/* Convert the value to a byte array, and trim it. */
					temp = ByteCalc.longToByteArray(Long.valueOf(hexValue, 16));
					temp = ByteCalc.subArray(
						temp, (int) (temp.length - byteLength), temp.length);
					
					/* Add entry to table file. */
					specialCharSet.addSpecialChar(temp, startValue);
				}
			}
		}
		
		/* Close file stream. */
		br.close();
	}
	
	/**
	 * Takes a table line, and extracts the hexadecimal value from it.
	 * 
	 * @param tableLine
	 * @return Hex value, or null if line is invalid.
	 */
	private String getHexValue(String tableLine) {
		String hexString;
		
		/*
		 * Check for the first '=' sign, and dump all text
		 * between the beginning of the string and it.
		 */
		for (int i = 0; i < tableLine.length(); i++) {
			if (tableLine.charAt(i) == '=') {
				hexString = tableLine.substring(0, i);
				
				/* Remove leading/trailing whitespace */
				hexString = hexString.trim(); 
				
				/* The string must be of size 1 or more. */
				if (hexString.length() < 1) {
					return null;
				}
				
				/* Check if the substring contains valid hex. */
				if (isValidHex(hexString)) {
					return hexString;
					
					/* If not, it might still be a special character. */
					} else if (specialCharSet.isSpecialCharacter("" + 
														hexString.charAt(0))) {

						specialCharSet.addSpecialChar(
						ByteCalc.longToByteArray(Long.getLong(hexString)), 
						hexString.charAt(0));
					
				} else {
				
					/* 
					 * This is not a valid hex string, and 
					 * not a special character.
					 */
					return null;
				}
			}
		}
		
		/* No '=' found. */
		return null;
	}
	
	/**
	 * Returns the text-part of a tablefile entry.
	 * 
	 * @param tblStr The tablefile entry.
	 * @return String containing table text, or null if entry is incorrectly 
	 * 		   formatted.
	 */
	private String getTextValue(String tblStr)
	{
		/*
		 * Check for the first '=' sign, and dump all text
		 * between the beginning of the string and it.
		 */
		for (int i = 0; i < tblStr.length(); i++) {
			
			/* Only return the substring if the string length is 1 or higher. */
			if (tblStr.charAt(i) == '=' && tblStr.length() > i) {
				return tblStr.substring(i + 1, tblStr.length());
			}
		}
		
		/* No '='. */
		return null;
	}
	
	/**
	 * Checks if a string consists of hex-only characters.
	 * 
	 * @param hexString String containing a hex value.
	 * @return True if hex value does not contain illegal characters,
	 * 		   otherwise null.
	 */
	private boolean isValidHex(String hexString) {
		char currentChar;
		
		/* Has to contain at least one value. */
		if (hexString == null || hexString.length() < 1) {
			return false;
		}
		
		/* Check each character for invalid signs. */
		for (int i = 0; i < hexString.length(); i++) {
			currentChar = hexString.charAt(i);
			
			if (currentChar < 'A' || currentChar > 'F') {
				if (currentChar < '0' || currentChar > '9') {
				
					return false; // Illegal character
				}
			}
		}
		
		/* Return true if no illegal characters were found. */
		return true;
	}
	
	private void setTextMaxLength(int newLength) {
		if (newLength > textMaxLength) {
			textMaxLength = newLength;
		}
	}
	
	private void setHexMaxLength(int newLength) {
		if (newLength > hexMaxLength) {
			hexMaxLength = newLength;
		}
	}
	
	/**
	 * Converts a long into a byte array. The length
	 * of the array is determined by the number of
	 * bytes it's supposed to store.
	 * @param value Value to be converted.
	 * @param bytes Byte array of value.
	 * @return 'bytes' long byte array of value.
	 */
	private byte[] getBytes(Long value, int bytes) {
		byte[] ret = ByteCalc.longToByteArray(value);
		ret = ByteCalc.subArray(ret, (int) (ret.length - bytes), ret.length);
		return ret;

	}

}
